package com.umeox.zikrringtest.single_test

import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.*

/**
 * 祈祷时间计算器
 */
internal class PrayerTimeCalc {

    companion object {
        fun addZero(t: Int): String {
            return if (t < 10) "0$t" else t.toString()
        }

        fun deleteZero(str: String): Int {
            return if (str[0] == '0') {
                return str.substring(1, str.length).toInt()
            } else {
                str.toInt()
            }
        }

        fun getOffsetDateByDay(
            amount: Int,
            currentDate_yMd: String,
            dateFormat: SimpleDateFormat
        ): String {
            val currentDate = dateFormat.parse(currentDate_yMd)
            val calendar = Calendar.getInstance()
            calendar.time = currentDate!!
            calendar.add(Calendar.DAY_OF_MONTH, amount)
            return dateFormat.format(calendar.time)
        }
    }

    /**
     *各个礼拜时间
     */
    private var fajrTime = ""
    private var sunriseTime = ""
    private var dhuhrTime = ""
    private var asrTime = ""
    private var sunsetTime = ""
    private var maghribTime = ""
    private var ishaTime = ""
    private var midnight = ""

    //派系
    private var mFactionsIndex = 0

    //祈祷参数
    private lateinit var methodParams: DoubleArray

    private var asrJuristic = 0

    private var adjustHighLats = 3

    private var mTimeZone = 0.0


    private var lat: Double = 0.0
    private var lng: Double = 0.0

    private var jDate = 0.0

    fun updateParams(
        params: DoubleArray,
        factionsIndex: Int,
        timeZone: Double,
        latitude: Double,
        longitude: Double
    ) {
        methodParams = params
        mFactionsIndex = factionsIndex
        asrJuristic = mFactionsIndex
        mTimeZone = timeZone
        lat = latitude
        lng = longitude
    }

    fun getPrayerTimes(
        year: Int, month: Int, day: Int
    ): MutableList<String> {
        val date = year.toString() + "-" + addZero(month) + "-" + addZero(day)
        val tomorrowDate = getOffsetDateByDay(1, date, SimpleDateFormat("yyyy-MM-dd", Locale.CHINA))
        val tomorrowYear = deleteZero(tomorrowDate.split("-")[0])
        val tomorrowMonth = deleteZero(tomorrowDate.split("-")[1])
        val tomorrowDay = deleteZero(tomorrowDate.split("-")[2])

        jDate = julianDate(year, month, day)
        val lonDiff = lng / (15.0 * 24.0)
        jDate -= lonDiff
        val todayDoubleArray = computeDayTimes2()

        jDate = julianDate(tomorrowYear, tomorrowMonth, tomorrowDay)
        jDate -= lonDiff
        val tomorrowDoubleArray = computeDayTimes2()

        val sunset = todayDoubleArray[4]
        val midnightDouble = if (mFactionsIndex == 0) {
            val sunrise = tomorrowDoubleArray[1]
            val diff = sunrise - sunset + 24
            sunset + (diff / 2)
        } else {
            val fajr = tomorrowDoubleArray[0]
            val diff = fajr - sunset + 24
            sunset + (diff / 2)
        }

        fajrTime = adjustTimesFormat2(todayDoubleArray[0])
        sunriseTime = adjustTimesFormat2(todayDoubleArray[1])
        dhuhrTime = adjustTimesFormat2(todayDoubleArray[2])
        asrTime = adjustTimesFormat2(todayDoubleArray[3])
        sunsetTime = adjustTimesFormat2(todayDoubleArray[4])
        maghribTime = adjustTimesFormat2(todayDoubleArray[5])
        ishaTime = adjustTimesFormat2(todayDoubleArray[6])
        midnight = adjustTimesFormat2(midnightDouble % 24)

        return mutableListOf(
            fajrTime,
            sunriseTime,
            dhuhrTime,
            asrTime,
            sunsetTime,
            maghribTime,
            ishaTime,
            midnight
        )
    }

    // ---------------------- Julian Date Functions -----------------------
    // calculate julian date from a calendar date
    private fun julianDate(year: Int, month: Int, day: Int): Double {
        var yearValue = year
        var monthValue = month
        if (monthValue <= 2) {
            yearValue -= 1
            monthValue += 12
        }
        val mA = floor(yearValue / 100.0)
        val mB = 2 - mA + floor(mA / 4.0)
        return (floor(365.25 * (yearValue + 4716))
                + floor(30.6001 * (monthValue + 1)) + day + mB) - 1524.5
    }

    // convert times array to given time format
    private fun adjustTimesFormat2(time: Double): String {
        return floatToTime24(time)
    }

    // convert double hours to 24h format
    private fun floatToTime24(time: Double): String {
        var timeValue = time
        val result: String
        if (java.lang.Double.isNaN(timeValue)) {
            return "-----"
        }
        timeValue = fixHour(timeValue + 0.5 / 60.0) // add 0.5 minutes to round
        val hours = floor(timeValue).toInt()
        val minutes = floor((timeValue - hours) * 60.0)
        result = if (hours in 0..9 && minutes >= 0 && minutes <= 9) {
            "0" + hours + ":0" + minutes.roundToInt()
        } else if (hours in 0..9) {
            "0" + hours + ":" + minutes.roundToInt()
        } else if (minutes in 0.0..9.0) {
            hours.toString() + ":0" + minutes.roundToInt()
        } else {
            hours.toString() + ":" + minutes.roundToInt()
        }
        return result
    }

    // range reduce hours to 0..23
    private fun fixHour(a: Double): Double {
        var aValue = a
        aValue -= 24.0 * floor(aValue / 24.0)
        aValue = if (aValue < 0) aValue + 24 else aValue
        return aValue
    }

    // compute prayer times at given julian date
    private fun computeDayTimes2(): DoubleArray {
        var times = doubleArrayOf(5.0, 6.0, 12.0, 13.0, 18.0, 18.0, 18.0) // default times
        times = computeTimes(times)
        times = adjustTimes(times)
        times = tuneTimes(times)
        return times
    }

    // adjust times in a prayer time array
    private fun adjustTimes(times: DoubleArray): DoubleArray {
        var timesValue = times
        for (i in timesValue.indices) {
            timesValue[i] += mTimeZone - lng / 15
        }

        timesValue[2] += (0 / 60).toDouble() // Dhuhr
        if (methodParams[1].toInt() == 1) {   // Maghrib
            timesValue[5] = timesValue[4] + methodParams[2] / 60
        }
        if (methodParams[3].toInt() == 1) // Isha
        {
            timesValue[6] = timesValue[5] + methodParams[4] / 60
        }
        if (adjustHighLats != 0) {
            timesValue = adjustHighLatTimes(timesValue)
        }
        return timesValue
    }

    private fun tuneTimes(times: DoubleArray): DoubleArray {
        for (i in times.indices) {
            times[i] = times[i]
        }
        return times
    }

    // adjust Fajr, Isha and Maghrib for locations in higher latitudes
    private fun adjustHighLatTimes(times: DoubleArray): DoubleArray {
        val nightTime = timeDiff(times[4], times[1]) // sunset to sunrise

        // Adjust Fajr
        val mFajrDiff = nightPortion(methodParams[0]) * nightTime
        if (java.lang.Double.isNaN(times[0]) || timeDiff(times[0], times[1]) > mFajrDiff) {
            times[0] = times[1] - mFajrDiff
        }

        // Adjust Isha
        val mIshaAngle: Double = if (methodParams[3].toInt() == 0) methodParams[4] else 18.0
        val mIshaDiff = nightPortion(mIshaAngle) * nightTime
        if (java.lang.Double.isNaN(times[6]) || timeDiff(times[4], times[6]) > mIshaDiff) {
            times[6] = times[4] + mIshaDiff
        }

        // Adjust Maghrib
        val mMaghribAngle: Double = if (methodParams[1].toInt() == 0) methodParams[2] else 4.0
        val mMaghribDiff = nightPortion(mMaghribAngle) * nightTime
        if (java.lang.Double.isNaN(times[5]) || timeDiff(times[4], times[5]) > mMaghribDiff) {
            times[5] = times[4] + mMaghribDiff
        }
        return times
    }

    // the night portion used for adjusting times in higher latitudes
    private fun nightPortion(angle: Double): Double {
        var calc = 0.0
        when (adjustHighLats) {
            3 -> calc = angle / 60.0
            1 -> calc =
                0.5
            2 -> calc = 0.14286
        }
        return calc
    }

    // ---------------------- Misc Functions -----------------------
    // compute the difference between two times
    private fun timeDiff(time1: Double, time2: Double): Double {
        return fixHour(time2 - time1)
    }

    // ---------------------- Compute Prayer Times -----------------------
    // compute prayer times at given julian date
    private fun computeTimes(times: DoubleArray): DoubleArray {
        val t = dayPortion(times)
        val mFajr = computeTime(
            180 - methodParams[0], t[0]
        )
        val mSunrise = computeTime(180 - 0.833, t[1])
        val mDhuhr = computeMidDay(t[2])
        val mAsr = computeAsr((1 + asrJuristic).toDouble(), t[3])
        val mSunset = computeTime(0.833, t[4])
        val mMaghrib = computeTime(
            methodParams[2], t[5]
        )
        val mIsha = computeTime(
            methodParams[4], t[6]
        )
        return doubleArrayOf(mFajr, mSunrise, mDhuhr, mAsr, mSunset, mMaghrib, mIsha)
    }

    // compute time for a given angle G
    private fun computeTime(G: Double, t: Double): Double {
        val mD = sunDeclination(jDate + t)
        val mZ = computeMidDay(t)
        val mBeg = -dsin(G) - dsin(mD) * dsin(lat)
        val mMid = dcos(mD) * dcos(lat)
        val mV = darccos(mBeg / mMid) / 15.0
        return mZ + if (G > 90) -mV else mV
    }

    // compute declination angle of sun
    private fun sunDeclination(jd: Double): Double {
        return sunPosition(jd)[0]
    }

    // ---------------------- Calculation Functions -----------------------
    // References:
    // http://www.ummah.net/astronomy/saltime
    // http://aa.usno.navy.mil/faq/docs/SunApprox.html
    // compute declination angle of sun and equation of time
    private fun sunPosition(jd: Double): DoubleArray {
        val mD = jd - 2451545
        val g = fixangle(357.529 + 0.98560028 * mD)
        val q = fixangle(280.459 + 0.98564736 * mD)
        val mL = fixangle(q + 1.915 * dsin(g) + 0.020 * dsin(2 * g))

        // double R = 1.00014 - 0.01671 * [self dcos:g] - 0.00014 * [self dcos:
        // (2*g)];
        val e = 23.439 - 0.00000036 * mD
        val d = darcsin(dsin(e) * dsin(mL))
        var mRA = darctan2(dcos(e) * dsin(mL), dcos(mL)) / 15.0
        mRA = fixHour(mRA)
        val mEqT = q / 15.0 - mRA
        val sPosition = DoubleArray(2)
        sPosition[0] = d
        sPosition[1] = mEqT
        return sPosition
    }

    // degree arctan2
    private fun darctan2(y: Double, x: Double): Double {
        val `val` = atan2(y, x)
        return radiansToDegrees(`val`)
    }

    // degree arcsin
    private fun darcsin(x: Double): Double {
        val `val` = asin(x)
        return radiansToDegrees(`val`)
    }

    // radian to degree
    private fun radiansToDegrees(alpha: Double): Double {
        return alpha * 180.0 / Math.PI
    }

    // ---------------------- Trigonometric Functions -----------------------
    // range reduce angle in degrees.
    private fun fixangle(a: Double): Double {
        var aValue = a
        aValue -= 360 * floor(aValue / 360.0)
        aValue = if (aValue < 0) aValue + 360 else aValue
        return aValue
    }

    // compute mid-day (Dhuhr, Zawal) time
    private fun computeMidDay(t: Double): Double {
        val mT = equationOfTime(jDate + t)
        return fixHour(12 - mT)
    }

    // compute equation of time
    private fun equationOfTime(jd: Double): Double {
        return sunPosition(jd)[1]
    }

    // degree sin
    private fun dsin(d: Double): Double {
        return sin(degreesToRadians(d))
    }

    // degree cos
    private fun dcos(d: Double): Double {
        return cos(degreesToRadians(d))
    }

    // deree to radian
    private fun degreesToRadians(alpha: Double): Double {
        return alpha * Math.PI / 180.0
    }

    // degree arccos
    private fun darccos(x: Double): Double {
        val `val` = acos(x)
        return radiansToDegrees(`val`)
    }

    // compute the time of Asr
    // Shafii: step=1, Hanafi: step=2
    private fun computeAsr(step: Double, t: Double): Double {
        val mD = sunDeclination(jDate + t)
        val mG = -darccot(step + dtan(abs(lat - mD)))
        return computeTime(mG, t)
    }

    // degree tan
    private fun dtan(d: Double): Double {
        return tan(degreesToRadians(d))
    }

    // degree arccot
    private fun darccot(x: Double): Double {
        val `val` = atan2(1.0, x)
        return radiansToDegrees(`val`)
    }

    // convert hours to day portions
    private fun dayPortion(times: DoubleArray): DoubleArray {
        for (i in 0..6) {
            times[i] = times[i] / 24
        }
        return times
    }
}